﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Engine.Graphics;

namespace Engine.Physics
{
    public class BoxCollider : Collider
    {
        private bool isSpriteBased;

        private Rectangle baseAABB;
        private Rectangle BaseAABB
        {
            get
            {
                return new Rectangle((int)Math.Round((gameObject.rigidbody != null ? gameObject.rigidbody.OwnPosition.X : gameObject.transform.LocalPosition.X) + (isSpriteBased ? -gameObject.sprite.Origin.X : baseAABB.X)),
                                     (int)Math.Round((gameObject.rigidbody != null ? gameObject.rigidbody.OwnPosition.Y : gameObject.transform.LocalPosition.Y) + (isSpriteBased ? -gameObject.sprite.Origin.Y : baseAABB.Y)),
                                     (int)Width,
                                     (int)Height);
            }
        }

        public OBB OrientedBoundingBox
        {
            get { return Shape as OBB; }
            private set { Shape = value; }
        }


        public override float Width { get { return (isSpriteBased ? gameObject.sprite.Width : baseAABB.Width); } }
        public override float Height { get { return (isSpriteBased ? gameObject.sprite.Height : baseAABB.Height); } }

        public BoxCollider()
        {
            isSpriteBased = true;
        }

        public BoxCollider(bool _isStatic, bool _isSpriteBased)
        {
            IsStatic = _isStatic;
            isSpriteBased = _isSpriteBased;
            IsCentered = false;
        }

        public BoxCollider(Rectangle _baseAABB)
        {
            baseAABB = _baseAABB;
            IsCentered = false;
            if (IsCentered)
                baseAABB = new Rectangle(-_baseAABB.Width / 2 + _baseAABB.X, -_baseAABB.Height / 2 + _baseAABB.Y, _baseAABB.Width, _baseAABB.Height);
            else
                baseAABB = _baseAABB;
            isSpriteBased = false;
        }

        public BoxCollider(Rectangle _baseAABB, bool _isCentered)
        {
            IsCentered = _isCentered;
            if (IsCentered)
                baseAABB = new Rectangle(-_baseAABB.Width / 2 + _baseAABB.X, -_baseAABB.Height / 2 + _baseAABB.Y, _baseAABB.Width, _baseAABB.Height);
            else
                baseAABB = _baseAABB;
            isSpriteBased = false;
        }

        public override void Awake()
        {
            base.Awake();
            RecalcOBB();
        }

        public override void LateUpdate(GameTime _gameTime)
        {
            base.LateUpdate(_gameTime);
            List<Vector2> screenPoints = new List<Vector2>(OrientedBoundingBox.Corners);
            for (int i = 0; i < screenPoints.Count; i++)
                screenPoints[i] = GraphicsEngine.Instance.MainCamera.WorldToScreen(screenPoints[i]);
            GraphicsEngine.Instance.DrawGizmo(screenPoints, Color.Green);
        }


        public void SetBaseAABB(Rectangle _aabb)
        {
            if (IsCentered)
                baseAABB = new Rectangle(-_aabb.Width / 2 + _aabb.X, -_aabb.Height / 2 + _aabb.Y, _aabb.Width, _aabb.Height);
            else
                baseAABB = _aabb;
            RecalcAABB();
            RecalcOBB();
        }

        public void SetBaseAABB(Rectangle _aabb, bool _centered)
        {
            IsCentered = _centered;
            SetBaseAABB(_aabb);
        }

        public void UpdateOBB()
        {
            if (!gameObject.transform.RotationChanged || (gameObject.rigidbody != null && gameObject.rigidbody.OwnRotation == gameObject.transform.Rotation))
            {
                if (gameObject.transform.PositionChanged || (gameObject.rigidbody != null && gameObject.rigidbody.OwnPosition != Position))
                {
                    OrientedBoundingBox.Offset(gameObject.transform.PositionDiff);
                }
            }
            else
            {
                RecalcOBB();
            }
        }

        public void RecalcOBB()
        {
            Vector2 OBBPos;
            if ((isSpriteBased && gameObject.sprite.IsCentered) || IsCentered)
            {
                OBBPos = gameObject.rigidbody != null ? gameObject.rigidbody.newPosition : gameObject.transform.LocalPosition;
                if (!isSpriteBased)
                    OBBPos -= new Vector2(Position.X - (BaseAABB.X + BaseAABB.Width / 2), Position.Y - (BaseAABB.Y + BaseAABB.Height / 2));
            }
            else
            {
                float cosAngle = (float)Math.Cos(gameObject.rigidbody != null ? gameObject.rigidbody.OwnRotation : gameObject.transform.Rotation);
                float sinAngle = (float)Math.Sin(gameObject.rigidbody != null ? gameObject.rigidbody.OwnRotation : gameObject.transform.Rotation);
                OBBPos = new Vector2((BaseAABB.Width / 2 * cosAngle) - (BaseAABB.Height / 2 * sinAngle),
                                             (BaseAABB.Height / 2 * cosAngle) + (BaseAABB.Width / 2 * sinAngle)) +
                         (gameObject.rigidbody != null ? gameObject.rigidbody.newPosition : gameObject.transform.LocalPosition);
            }

            if (OrientedBoundingBox != null)
            {
                OrientedBoundingBox.Reset(OBBPos,
                                              BaseAABB.Width,
                                              BaseAABB.Height,
                                              gameObject.rigidbody != null ? gameObject.rigidbody.OwnRotation : gameObject.transform.Rotation);
            }
            else
            {
                OrientedBoundingBox = new OBB(OBBPos,
                                              BaseAABB.Width,
                                              BaseAABB.Height,
                                              gameObject.rigidbody != null ? gameObject.rigidbody.OwnRotation : gameObject.transform.Rotation);
            }
        }

        public override void UpdateShape()
        {
            base.UpdateShape();
            RecalcOBB();
        }

        public override void RecalcShape()
        {
            base.RecalcShape();
            RecalcOBB();
        }

        public override void RecalcAABB()
        {
            mAABB = RecalcAABBFromRectangle();
            aabbLocation = new Vector2(mAABB.Location.X, mAABB.Location.Y);
        }

        private Rectangle RecalcAABBFromRectangle()
        {
            Matrix rotation = Matrix.CreateRotationZ(-(gameObject.rigidbody != null ? gameObject.rigidbody.OwnRotation : gameObject.transform.Rotation));
            float[,] m = new float[2, 2];
            m[0, 0] = rotation.M11;
            m[0, 1] = rotation.M12;
            m[1, 0] = rotation.M21;
            m[1, 1] = rotation.M22;

            float[] min = new float[2];
            float[] max = new float[2];

            if (isSpriteBased)
            {
                min[0] = -gameObject.sprite.Origin.X * gameObject.sprite.Scale.X;
                min[1] = -gameObject.sprite.Origin.Y * gameObject.sprite.Scale.Y;
            }
            else if (IsCentered)
            {
                min[0] = baseAABB.X;
                min[1] = baseAABB.Y;
            }
            else
            {
                min[0] = baseAABB.X;
                min[1] = baseAABB.Y;
            }

            max[0] = BaseAABB.Width + min[0];
            max[1] = BaseAABB.Height + min[1];

            float[] newMin = new float[2];
            float[] newMax = new float[2];

            for (int i = 0; i < 2; i++)
            {
                for (int j = 0; j < 2; j++)
                {
                    float e = m[i, j] * min[j];
                    float f = m[i, j] * max[j];

                    if (e < f)
                    {
                        newMin[i] += e;
                        newMax[i] += f;
                    }
                    else
                    {
                        newMin[i] += f;
                        newMax[i] += e;
                    }
                }
            }

            return new Rectangle((int)Math.Round(newMin[0] + (isSpriteBased ? gameObject.sprite.Origin.X + BaseAABB.X : Position.X)),
                                  (int)Math.Round(newMin[1] + (isSpriteBased ? gameObject.sprite.Origin.Y + BaseAABB.Y : Position.Y)),
                                  (int)Math.Round(newMax[0] - newMin[0]),
                                  (int)Math.Round(newMax[1] - newMin[1]));
        }

    }
}
